package com.blacktea.mongo.service;

import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.blacktea.mongo.entites.dto.MongoConditionDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @description:
 * @author: black tea
 * @date: 2021/8/30 10:14
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class MongoServiceImpl<T> implements MongoService<T> {

    private final MongoTemplate mongoTemplate;

    /**
     * 根据单个条件获取一条记录
     * @param var1 数据类型
     * @param var2 查询字段名称
     * @param var3 查询字段值
     * @return T
     */
    @Override
    public T getOneByOne(Class<T> var1, String var2, Object var3){
        return this.getOneByOne(var1,var2,var3,null);
    }

    /**
     * 根据多个条件获取一条记录
     * @param var1 数据类型
     * @param var2 Map类型,根据 k,v 进行条件查询(k：对应查询字段名称,v: 对应查询字段值)
     * @return T
     */
    @Override
    public T getOneByMany(Class<T> var1, Map<String,Object> var2){
        return this.getOneByMany(var1,var2);
    }

    /**
     * 根据单个条件获取一条记录
     * @param var1 数据类型
     * @param var2 查询字段名称
     * @param var3 查询字段值
     * @param collectionName 集合名称
     * @return T
     */
    @Override
    public T getOneByOne(Class<T> var1, String var2, Object var3, String collectionName){
        Query query = new Query();
        query.addCriteria(Criteria.where(var2).is(var3));
        if (StringUtils.isEmpty(collectionName)){
            return mongoTemplate.findOne(query, var1);
        }
       return mongoTemplate.findOne(query,var1,collectionName);
    }

    /**
     * 根据多个条件获取一条记录(条件均为and)
     * @param var1 数据类型
     * @param var2 Map类型,根据 k,v 进行条件查询(k：对应查询字段名称,v: 对应查询字段值)
     * @param collectionName 集合名称
     * @return T
     */
    @Override
    public T getOneByMany(Class<T> var1, Map<String,Object> var2, String collectionName){
        Query query = new Query();
        var2.forEach((k,v) -> {
            query.addCriteria(Criteria.where(k).is(v));
        });
        if (StringUtils.isEmpty(collectionName)){
            return mongoTemplate.findOne(query,var1);
        }
        return mongoTemplate.findOne(query,var1,collectionName);
    }

    /**
     *  根据多个dto获取一条记录(条件根据dto属性and判断)
     * @param var1 数据类型
     * @param dtos mongo 条件 DTO
     * @param collectionName 集合名称
     * @return T
     */
    @Override
    public T getOneByMany(Class<T> var1, List<MongoConditionDTO> dtos, String collectionName){
        List<T> listByCondition = this.getListByCondition(var1, dtos, collectionName);
        if (CollectionUtils.isEmpty(listByCondition)){
            return null;
        }else if (listByCondition.size() > 1){
            log.error("查询单条mongo记录时,出现多条记录,返回第一条,并提示,当前查询dto:{}", JSONUtil.toJsonStr(dtos));
        }
        return listByCondition.get(0);
    }

    /**
     * 根据单个条件查询集合
     * @param var1 数据类型
     * @param var2 查询字段名称
     * @param var3 查询字段值
     * @return
     */
    @Override
    public List<T> getListByOne(Class<T> var1, String var2, Object var3){
        return this.getListByOne(var1,var2,var3);
    }

    /**
     * 根据单个条件查询单条记录
     * @param var1 数据类型
     * @param var2 查询字段名称
     * @param var3 查询字段值
     * @param collectionName 集合名称
     * @return
     */
    @Override
    public List<T> getListByOne(Class<T> var1, String var2, Object var3, String collectionName){
        Query query = new Query();
        query.addCriteria(Criteria.where(var2).is(var3));
        if (StringUtils.isEmpty(collectionName)){
           return mongoTemplate.find(query,var1);
        }
        return mongoTemplate.find(query,var1,collectionName);
    }

    /**
     * 根据多个条件获取多条记录(条件均为and)
     * @param var1 数据类型
     * @param var2 Map类型,根据 k,v 进行条件查询(k：对应查询字段名称,v: 对应查询字段值)
     * @return List<T>
     */
    @Override
    public List<T> getListByMany(Class<T> var1, Map<String, Object> var2){
        return this.getListByMany(var1,var2,null);
    }

    /**
     * 根据多个条件获取多条记录(条件均为and,Criteria操作均为is)
     * @param var1 数据类型
     * @param var2 Map类型,根据 k,v 进行条件查询(k：对应查询字段名称,v: 对应查询字段值)
     * @param collectionName 集合名称
     * @return List<T>
     */
    @Override
    public List<T> getListByMany(Class<T> var1, Map<String, Object> var2, String collectionName){
        Query query = new Query();
        var2.forEach((k,v) -> {
            query.addCriteria(Criteria.where(k).is(v));
        });
        if (StringUtils.isEmpty(collectionName)){
            return mongoTemplate.find(query,var1);
        }
        return mongoTemplate.find(query,var1,collectionName);
    }

    /**
     *  根据多个dto获取一条记录(条件根据dto属性and判断)
     * @param var1 数据类型
     * @param dtos mongo 条件 DTO
     * @param collectionName 集合名称
     * @return List<T>
     */
    @Override
    public List<T> getListByCondition(Class<T> var1, List<MongoConditionDTO> dtos, String collectionName){
        if (CollectionUtils.isEmpty(dtos)){
            return null;
        }
        Query query = new Query();
        query.addCriteria(this.conditionCombination(dtos));
        if (StringUtils.isEmpty(collectionName)){
            return mongoTemplate.find(query,var1);
        }
        return mongoTemplate.find(query,var1,collectionName);
    }

    /**
     *  根据多个dto获取一条记录(条件根据dto属性and判断)
     * @param var1 数据类型
     * @param dtos mongo 条件 DTO
     * @return List<T>
     */
    @Override
    public List<T> getListByCondition(Class<T> var1, List<MongoConditionDTO> dtos){
        return this.getListByCondition(var1,dtos,null);
    }

    /**
     * 获取该集合所有的数据
     * @param var1 数据类型
     * @return st<T>
     */
    @Override
    public List<T> getList(Class<T> var1){
        return this.getList(var1,null);
    }

    /**
     * 获取该集合所有的数据
     * @param var1 数据类型
     * @param collectionName 集合名称
     * @return st<T>
     */
    @Override
    public List<T> getList(Class<T> var1, String collectionName){
        if (StringUtils.isEmpty(collectionName)){
            return mongoTemplate.findAll(var1);
        }
        return mongoTemplate.findAll(var1,collectionName);
    }

    @Override
    public Page<T> getPage(Class<T> var1, String collectionName, PageRequest pageRequest) {
       return this.getPageCommonByCondition(var1, collectionName, null, pageRequest);
    }

    @Override
    public Page<T> getPage(Class<T> var1, PageRequest pageRequest) {
        return this.getPage(var1,null,pageRequest);
    }

    @Override
    public Page<T> getPageByCondition(Class<T> var1, String collectionName, List<MongoConditionDTO> dtos, PageRequest pageRequest) {
        if (CollectionUtils.isEmpty(dtos)){
            return null;
        }
        return this.getPageCommonByCondition(var1, collectionName, dtos, pageRequest);
    }

    @Override
    public Page<T> getPageByCondition(Class<T> var1, List<MongoConditionDTO> dtos, PageRequest pageRequest) {
        return this.getPageCommonByCondition(var1, null, dtos, pageRequest);
    }

    @Override
    public long getCountAll(Class<T> var1) {
        return this.getCountAll(var1,null);
    }

    @Override
    public long getCountAll(Class<T> var1, String collectionName) {
        Query query = new Query();
        if (StringUtils.isEmpty(collectionName)){
            return mongoTemplate.count(query,var1);
        }
        return mongoTemplate.count(query,var1,collectionName);
    }

    @Override
    public long getCountByCondition(Class<T> var1, List<MongoConditionDTO> dtos) {
        return this.getCountByCondition(var1,dtos);
    }

    @Override
    public long getCountByCondition(Class<T> var1, String collectionName, List<MongoConditionDTO> dtos) {
        if (CollectionUtils.isEmpty(dtos)){
            return -1;
        }
        Query query = new Query();
        query.addCriteria(this.conditionCombination(dtos));
        if (StringUtils.isEmpty(collectionName)){
            return mongoTemplate.count(query,var1);
        }
        return mongoTemplate.count(query,var1,collectionName);
    }

    /**
     * 移除符合条件的数据
     * @param var1 数据类型
     * @param dtos mongo 条件 DTO
     * @param collectionName 集合名称
     * @return boolean
     */
    @Override
    public boolean removeByCondition(Class<?> var1, List<MongoConditionDTO> dtos, String collectionName){
        if (CollectionUtils.isEmpty(dtos)){
            return false;
        }
        Query query = new Query();
        query.addCriteria(this.conditionCombination(dtos));
        mongoTemplate.remove(query, var1, collectionName);
        return true;
    }

    /**
     * 移除一个
     * @param object 对象
     * @param collectionName 集合名称
     * @return boolean
     */
    @Override
    public boolean removeObj(Object object, String collectionName){
        mongoTemplate.remove(object,collectionName);
        return true;
    }

    /**
     * 保存这条object,并返回
     * @param objectToSave 对象
     * @param collectionName 集合名称
     * @return T
     */
    @Override
    public T save(T objectToSave, String collectionName){
      return mongoTemplate.save(objectToSave,collectionName);
    }

    /**
     * 保存多条数据,并返回
     * @param objectToSaves 数据集合
     * @param collectionName 集合名称
     * @return List<T>
     */
    @Override
    public List<T> saveAll(List<T> objectToSaves, String collectionName){
        if (CollectionUtils.isEmpty(objectToSaves)){
            return null;
        }
        List<T> result = new ArrayList<>();
        objectToSaves.forEach(o -> {
            result.add(this.save(o,collectionName));
        });
        return result;
    }

    /**
     * 修改满足条件的数据
     * @param var1 数据类型
     * @param dtos mongo 条件 DTO
     * @param updateMap Map类型,根据 k,v 进行条件查询(k：对应查询字段名称,v: 对应查询字段值)
     * @param collectionName 集合名称
     * @return boolean
     */
    @Override
    public boolean updateByCondition(Class<?> var1, List<MongoConditionDTO> dtos, Map<String,Object> updateMap , String collectionName){
        if (CollectionUtils.isEmpty(dtos)){
            return false;
        }
        if (MapUtil.isEmpty(updateMap)){
            return false;
        }
        Query query = new Query();
        query.addCriteria(this.conditionCombination(dtos));
        Update update = new Update();
        updateMap.forEach((k,v) ->{
            update.set(k,v);
        });
        mongoTemplate.updateMulti(query, update, var1, collectionName);
        return true;
    }

    private Criteria conditionCombination(List<MongoConditionDTO> dtos){
        Criteria criteria = new Criteria();
        int size = dtos.size();
        // 直接设置最大,避免超出范围，但会出现多余的空值！
        Criteria[] add = new Criteria[size];
        Criteria[] or = new Criteria[size];
        AtomicInteger addIndex = new AtomicInteger(0);
        AtomicInteger orIndex = new AtomicInteger(0);
        dtos.forEach(d ->{
            if (!d.isAnd()){
                or[orIndex.intValue()] = this.criteriaOperation(d);
                orIndex.incrementAndGet();
            }else {
                add[addIndex.intValue()] = this.criteriaOperation(d);
                addIndex.incrementAndGet();
            }
        });
        if (addIndex.intValue() != 0){
            // 解决上面导致的多余空值问题!
            Criteria[] addCriteria = Arrays.stream(add)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList())
                    .toArray(new Criteria[addIndex.intValue()]);
            criteria = criteria.andOperator(addCriteria);
        }
        if (orIndex.intValue() != 0){
            Criteria[] orCriteria = Arrays.stream(or)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList())
                    .toArray(new Criteria[orIndex.intValue()]);
            criteria = criteria.orOperator(orCriteria);
        }
        return criteria;
    }

    private Criteria criteriaOperation(MongoConditionDTO d){
        Criteria criteria = Criteria.where(d.getKey());
        switch (d.getCriteriaOperation()){
            case IS:
                criteria.is(d.getValue());
                break;
            case GTE:
                criteria.gte(d.getValue());
                break;
            case LET:
                criteria.lte(d.getValue());
                break;
            case REGEX:
                criteria.regex((String) d.getValue());
                break;
            default:
                return null;
        }
        return criteria;
    }

    private Page<T> getPageCommonByCondition(Class<T> var1, String collectionName, List<MongoConditionDTO> dtos, PageRequest pageRequest){
        long count = 0L;
        Query query = new Query();
        if (!CollectionUtils.isEmpty(dtos)){
            query.addCriteria(this.conditionCombination(dtos));
        }
        if (StringUtils.isEmpty(collectionName)){
            count = mongoTemplate.count(query,var1);
        }else {
            count = mongoTemplate.count(query,collectionName);
        }
        query.with(pageRequest);
        List<T> ts = new ArrayList<>();
        if (StringUtils.isEmpty(collectionName)){
            ts = mongoTemplate.find(query,var1);
        }else {
            ts = mongoTemplate.find(query,var1,collectionName);
        }
        return new PageImpl<>(ts,pageRequest,count);
    }

}
